/*
 This file is part of Desu: MapleStory v62 Server Emulator
 Copyright (C) 2014  Brent Rinchiuso <brentrin@gmail.com>

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package field;

import client.Client;
import client.packet.PacketCreator;
import java.awt.Point;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import player.Player;
import wz.WzObject;
import wz.common.WzDataTool;

/**
 *
 * @author Brent Rinchiuso
 */
public class Monster extends FieldLife {

    private int id;
    private int fh;
    private int exp;
    private int level;
    private int link;
    private Point pos;
    private int HP, MP;
    private int lHP, lMP;
    private int mobType;
    private int stance = 5;
    private boolean boss = false;
    private boolean aggro = false;
    private boolean undead = false;
    private List<SkillInfo> skills; // XXX check to see if skills have multiples, if so we need to go by id and level
    private List<CooldownInfo> cooldowns = new LinkedList<>();
    private final HashMap<Integer, AtomicInteger> takenDamage = new HashMap<>();

    public class SkillInfo {

        public int id;
        public int level;
    }

    public class CooldownInfo {

        public int id;
        public long start;
        public long duration;

        public long remaining() {
            return duration - System.currentTimeMillis() - start;
        }
    }

    private Monster() {
    }

    public Monster(Monster gen, Field f) {
        id = gen.id;
        exp = gen.exp;
        undead = gen.undead;
        level = gen.level;
        link = gen.link;
        HP = gen.HP;
        MP = gen.MP;
        lHP = HP;
        lMP = MP;
        mobType = gen.mobType;
        skills = gen.skills;
    }

    public Monster(int id, WzObject<?, ?> info) {
        this.id = id;
        exp = WzDataTool.getInteger(info, "info/exp", 0);
        level = WzDataTool.getInteger(info, "info/level", 1);
        link = WzDataTool.getInteger(info, "info/link", 0);
        HP = WzDataTool.getInteger(info, "info/maxHP", 0);
        MP = WzDataTool.getInteger(info, "info/maxMP", 0);
        mobType = WzDataTool.getInteger(info, "info/mobType", 0);
        undead = WzDataTool.getBoolean(info, "info/undead", false);
        skills = new LinkedList<>();
        WzObject<?, ?> skillData = info.getChildByPath("info/skill");
        if (skillData != null) {
            for (WzObject skill : skillData) {
                SkillInfo si = new SkillInfo();
                si.id = WzDataTool.getInteger(skill, "skill", 0);
                si.level = WzDataTool.getInteger(skill, "level", 0);
                skills.add(si);
            }
        }
    }

    public int getExperience() {
        return exp;
    }

    public int getLevel() {
        return level;
    }

    public int getLink() {
        return link;
    }

    public void applyMPChange(int val) {
        lMP += val;
    }

    public void damage(Player src, int damage) {
        int trueDamage = Math.min(lHP, damage);

        lHP -= trueDamage;

        synchronized (takenDamage) {
            if (takenDamage.containsKey(src.getOid())) {
                takenDamage.get(src.getOid()).addAndGet(trueDamage);
            } else {
                takenDamage.put(src.getOid(), new AtomicInteger(trueDamage));
            }
        }

        aggro = true;

        if (lHP <= 0) {
            if (getController() != NO_CONTROLLER) {
                src.getField().killMonster(this, src.getOid(), takenDamage);
                takenDamage.clear();
                // XXX if we want to do something special with the person to score the killing blow, do it here
                return;
            }
        }

        if (!boss) {
            src.getClient().write(PacketCreator.showMonsterHP(getOid(), Math.max(getRemainingHealthRatio(), 1)));
        } else {
            // XXX note: boss HP bar
        }
    }

    public int getDamageDealtBy(int oid) {
        if (takenDamage.containsKey(oid)) {
            return takenDamage.get(oid).get();
        }
        return 0;
    }

    public boolean isAlive() {
        return lHP > 0;
    }

    public boolean isBoss() {
        return boss;
    }

    public int getId() {
        return id;
    }

    public int getHP() {
        return lHP;
    }

    public int getRemainingHealthRatio() {
        return (int) Math.ceil(lHP * 100 / HP);
    }

    public int getMP() {
        return lMP;
    }

    public int getMaxHP() {
        return HP;
    }

    public int getMaxMP() {
        return MP;
    }

    public int getMobType() {
        return mobType;
    }

    public void setFh(int fh) {
        this.fh = fh;
    }

    @Override
    public int getFh() {
        return fh;
    }

    @Override
    public boolean isHidden() {
        return false; // mobs are never "hidden"
    }

    @Override
    public void toggleHidden() {
    }

    @Override
    public Point getPosition() {
        return pos;
    }

    @Override
    public void setPosition(Point p) {
        pos = p;
    }

    @Override
    public int getStance() {
        return stance;
    }

    @Override
    public void setStance(int s) {
        stance = s;
    }

    @Override
    public void sendSpawnData(Client c) {
        c.write(PacketCreator.spawnMonster(this, !boss)); // XXX idk lel
    }

    @Override
    public void sendDestroyData(Client c, boolean spc) {
        c.write(PacketCreator.removeMonster(getOid(), spc)); // destroy data -> remove ani, hmmm idk if this will cause d/c if true
    }

    @Override
    public void sendControlLoss(Client mc) {
        mc.write(PacketCreator.removeMonsterControl(this));
    }

    @Override
    public void sendControlGain(Client mc) {
        mc.write(PacketCreator.giveMonsterControl(this, false, false));
    }

    @Override
    public Type getObjectType() {
        return Type.MONSTER;
    }

    public boolean isUndead() { // XXX add undead check
        return undead;
    }

//    public void checkAggroStatus() {
//        int control = controller;
//        int srcControl = controller;
//        int mostDealt = getDamageDealtBy(controller);
//        synchronized (takenDamage) {
//            for (Entry<Integer, AtomicInteger> entry : takenDamage.entrySet()) {
//                if (entry.getValue().get() >= mostDealt) {
//                    Player p = (Player) field.getFieldObject(entry.getKey()); // bad casting possible
//                    if (p != null) {
//                        control = p.getOid();
//                        mostDealt = entry.getValue().get();
//                    }
//                }
//            }
//        }
//        if (srcControl != control) {
//            Player nControl = (Player) field.getFieldObject(control);
//            removeControl();
//            giveControl(nControl);
//        }
//    }

    public void triggerAggro() {
        aggro = true;
    }

    public boolean isAggressive() {
        return aggro;
    }

    public boolean hasSkills() {
        return !skills.isEmpty();
    }

    public int getSkillCount() {
        return skills.size();
    }

    public SkillInfo getRandomSkill() {
        return skills.get(new Random().nextInt(skills.size()));
    }

    public SkillInfo getSkill(int id) {
        for (SkillInfo s : skills) {
            if (s.id == id) {
                return s;
            }
        }
        return null;
    }

    public boolean hasSkill(int id, int level) {
        SkillInfo si = getSkill(id);
        if (si != null) {
            return si.level == level;
        }
        return false;
    }

    public CooldownInfo getCooldown(int id) {
        for (CooldownInfo c : cooldowns) {
            if (c.id == id) {
                return c;
            }
        }
        return null;
    }

    public boolean canUseSkill(int id, int healthThreshold, int mpCost) {
        CooldownInfo c = getCooldown(id);
        if (healthThreshold <= getRemainingHealthRatio()) {
            if (lMP > mpCost) {
                if (c != null && c.remaining() <= 0) {
                    cooldowns.remove(c);
                    return true;
                }
            }
        }
        return false;
    }
}
